import re
import sys
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons

def parse_process_info(file_path):
    try:
        with open(file_path, 'r') as file:
            lines = file.readlines()

        processes = {}
        header_found = False

        for line in lines:
            # Skip lines that start with "Mem", "CPU", or "Load average"
            if line.startswith(("Mem", "CPU", "Load average")):
                continue

            # Check for the header line that indicates the start of process information
            if re.match(r'^\s*PID\s+PPID\s+USER\s+STAT', line):
                header_found = True
                continue  # Skip the header line

            # Skip lines before header
            if not header_found:
                continue

            # Process the process information lines
            line = re.sub(r'(\d+)m(\d+)', r'\1m \2', line)  # Handle memory and percent concatenation
            parts = re.split(r'\s+', line.strip())

            # Check if we have at least 8 columns
            if len(parts) >= 8:
                # Extract PID, USER, and COMMAND
                pid = parts[0]
                user = parts[2]
                command = ' '.join(parts[8:])  # Join everything after the first 8 columns
                
                # Extract CPU percentage (7th column)
                try:
                    cpu_percentage = float(parts[7]) if len(parts) > 7 and parts[7].replace('.', '', 1).isdigit() else 0.0
                except ValueError:
                    continue

                # Store in dictionary if user is 'root'
                if user == 'root':
                    if pid not in processes:
                        processes[pid] = {
                            'USER': user,
                            'COMMAND': command,
                            'CPU': []
                        }
                    processes[pid]['CPU'].append(cpu_percentage)

        # Calculate average CPU for each process and filter results
        filtered_processes = {}
        for pid, info in processes.items():
            average_cpu = sum(info['CPU']) / len(info['CPU']) if info['CPU'] else 0
            if average_cpu > 1.0:
                filtered_processes[pid] = {
                    'USER': info['USER'],
                    'COMMAND': info['COMMAND'],
                    'AVERAGE_CPU': average_cpu,
                    'CPU_SAMPLES': info['CPU']
                }

        return filtered_processes
    except FileNotFoundError:
        print(f"Error: File '{file_path}' not found.")
        return {}
    except Exception as e:
        print(f"An error occurred: {e}")
        return {}

def save_sampling_points(process_info):
    # Plot CPU usage for each PID and store line objects for visibility control
    fig, ax = plt.subplots(figsize=(10, 6))
    lines = []
    labels = []
    commands = []  # Store commands for tooltips
    
    for pid, info in process_info.items():
        x_values = list(range(1, len(info['CPU_SAMPLES']) + 1))  # Sampling points
        y_values = info['CPU_SAMPLES']  # CPU usage values
        line, = ax.plot(x_values, y_values, label=f"PID {pid}")
        lines.append(line)  # Store the line object for toggling visibility
        labels.append(f"PID {pid}")
        commands.append(info['COMMAND'])  # Store command for tooltip

    # Add labels and title
    ax.set_xlabel("Sampling Point")
    ax.set_ylabel("CPU Usage (%)")
    ax.set_title("CPU Usage Over Time for Each PID (Average CPU > 1%)")
    ax.grid(True)

    # Create CheckButtons for each PID
    rax = plt.axes([0.85, 0.4, 0.1, 0.4])  # Position for the check buttons
    check = CheckButtons(rax, labels, [True] * len(labels))

    # Define the function to toggle line visibility
    def toggle_visibility(label):
        index = labels.index(label)
        lines[index].set_visible(not lines[index].get_visible())
        plt.draw()

    check.on_clicked(toggle_visibility)  # Bind the toggle function to check button

    # Function to display command on hover
    tooltip = ax.text(0, 0, '', fontsize=10, ha='center', va='center', color='black', bbox=dict(facecolor='white', alpha=0.8, edgecolor='none', pad=1))

    def on_hover(event):
        tooltip.set_visible(False)  # Hide tooltip initially
        if event.inaxes == ax:
            for line, command in zip(lines, commands):
                # Check if the mouse is near the line (using distance from the line)
                xdata = event.xdata
                if xdata is None:  # Ignore if xdata is None
                    continue
                ydata = line.get_ydata()
                x_values = line.get_xdata()
                # Calculate distance to the line
                if x_values.min() <= xdata <= x_values.max():
                    idx = (x_values >= xdata).argmax()  # Get the index of the nearest x value
                    if abs(ydata[idx] - event.ydata) < 1:  # Check if within a certain distance
                        tooltip.set_text(command)
                        tooltip.set_position((xdata, ydata[idx]))
                        tooltip.set_visible(True)
                        break

        plt.draw()

    # Connect the hover event
    fig.canvas.mpl_connect("motion_notify_event", on_hover)

    # Show the plot
    plt.subplots_adjust(right=0.8)  # Adjust plot to make space for check buttons
    plt.legend(loc="upper right")
    plt.show()

def main():
    # Check if a file path was provided as a command line argument
    if len(sys.argv) < 2:
        print("Usage: python script.py <file_path>")
        return

    file_path = sys.argv[1]
    process_info = parse_process_info(file_path)

    # Sort processes by average CPU in descending order and print the results
    sorted_processes = sorted(process_info.items(), key=lambda x: x[1]['AVERAGE_CPU'], reverse=True)

    for pid, info in sorted_processes:
        print(f"PID: {pid}, USER: {info['USER']}, COMMAND: {info['COMMAND']}, AVERAGE CPU: {info['AVERAGE_CPU']:.2f}")

    # Plot sampling points for each process with average CPU > 1
    save_sampling_points(process_info)

if __name__ == "__main__":
    main()
